"""
Update log:

@2024-11-19: add output dir setting

"""

import os
import platform
import tkinter as tk
#from tkinter.ttk.theme import Theme
from PIL import ImageTk
from tkinter import messagebox
from tkinter import filedialog
import socket
import time
import _thread
import threading
import logging
import datetime
import logging

from bin2fits import bin2fits
from header import FitsHeader

MSG_LEN = 16384

###############################################################################
# 全局变量
g_ips = []
camera_found = 0
cameras_found = 0       # 记录所有查找到的相机数量


###############################################################################
# 日志模块
class LogHandler(logging.Handler):
    def __init__(self, text_widget):
        logging.Handler.__init__(self)
        self.text_widget = text_widget

    def emit(self, record):
        msg = self.format(record)
        self.text_widget.insert(tk.END, msg + '\n')
        self.text_widget.see(tk.END)

###############################################################################


class mephisto_mosaic_camera:
    def __init__(self, IP='10.75.0.30', SOCK_PORT='9527'):
        self.lcoal_ip   = IP
        self.sock_port  = SOCK_PORT
        self.camera_ips = []                    # 记录发现的相机IP

        self.window = tk.Tk()
        # self.window.title('Camera Python')    # 设置窗口的标题
        self.window.geometry('900x900')         # 设置窗口的大小
        self.window.resizable(0,0)

        # self.window.iconbitmap('webcamoid.ico')
        # self.window.iconphoto(False, tk.PhotoImage(file='camera.png'))
        # self.window.iconphoto(True,tk.PhotoImage(file='camera.png'))
        self.window.iconphoto(False,tk.PhotoImage(file='./icon/camera.png'))

        self.window.wm_title("Mephisto Mosaic Camera GUI")

        #######################
        # 软件运行模式
        self.running_mode = 'debug' # default
        # self.running_mode = 'performance_test'  # 性能测试

        ######################################################################
        # IP网段和端口输入对话框标签
        tk.Label(self.window, text="HOST IP:", bg='gold').place(x=15, y=20, height=30, anchor='nw')
        tk.Label(self.window, text="PORT:", bg='gold').place(x=250, y=20, height=30, anchor='nw')

        # IP网段和端口输入对话框
        self.host_ip_var = tk.StringVar()
        self.host_port_var = tk.StringVar()
        self.en1 = tk.Entry(self.window, textvariable=self.host_ip_var)
        self.en2 = tk.Entry(self.window, textvariable=self.host_port_var)

        self.en1.place(x=75, y=20, width=100, height=30, anchor='nw')
        self.en1.insert(0, self.lcoal_ip)

        self.en2.place(x=300, y=20, width=60, height=30, anchor='nw')
        self.en2.insert(0, self.sock_port)

        ######################################################################
        # 添加搜索相机的按钮
        self.btn_search_cam = tk.Button(self.window, text='Find Camera', bg='gold', command=self.socket_find_camera)
        self.btn_search_cam.place(x=500, y=20, width=120, height=30, anchor='ne')

        ######################################################################
        # 复位配置按钮
        self.btn_reset_config = tk.Button(self.window, text='Reset Config', bg='gray', command=self.reset_config)
        self.btn_reset_config.place(x=800, y=20, width=120, height=30, anchor='ne')        

        ######################################################################
        # 相机1~4的IP输入端口
        self.ip_var_camera1 = tk.StringVar()
        self.ip_var_camera2 = tk.StringVar()
        self.ip_var_camera3 = tk.StringVar()
        self.ip_var_camera4 = tk.StringVar()
        self.ip_var_master  = tk.StringVar()
        self.ip_var_master_is_set = False

        self.entry_ip_var_camera1 = tk.Entry(self.window, textvariable=self.ip_var_camera1)
        self.entry_ip_var_camera2 = tk.Entry(self.window, textvariable=self.ip_var_camera2)
        self.entry_ip_var_camera3 = tk.Entry(self.window, textvariable=self.ip_var_camera3)
        self.entry_ip_var_camera4 = tk.Entry(self.window, textvariable=self.ip_var_camera4)
        self.entry_ip_var_master  = tk.Entry(self.window, textvariable=self.ip_var_master)

        self.entry_ip_var_camera1.insert(0, '')
        self.entry_ip_var_camera2.insert(0, '')
        self.entry_ip_var_camera3.insert(0, '')
        self.entry_ip_var_camera4.insert(0, '')
        self.entry_ip_var_master.insert(0, '')

        # 相机1~4的master/slave状态
        self.role_var_camera1   = tk.StringVar()
        self.role_var_camera2   = tk.StringVar()
        self.role_var_camera3   = tk.StringVar()
        self.role_var_camera4   = tk.StringVar()
        self.entry_master_status1 = tk.Entry(self.window, textvariable=self.role_var_camera1)
        self.entry_master_status2 = tk.Entry(self.window, textvariable=self.role_var_camera2)
        self.entry_master_status3 = tk.Entry(self.window, textvariable=self.role_var_camera3)
        self.entry_master_status4 = tk.Entry(self.window, textvariable=self.role_var_camera4)

        # 相机1~4的mount路径
        self.bindir_camera1_val = tk.StringVar()
        self.bindir_camera2_val = tk.StringVar()
        self.bindir_camera3_val = tk.StringVar()
        self.bindir_camera4_val = tk.StringVar()
        self.bindir_camera1 = tk.Entry(self.window, textvariable=self.bindir_camera1_val)
        self.bindir_camera2 = tk.Entry(self.window, textvariable=self.bindir_camera2_val)
        self.bindir_camera3 = tk.Entry(self.window, textvariable=self.bindir_camera3_val)
        self.bindir_camera4 = tk.Entry(self.window, textvariable=self.bindir_camera4_val)

        
        # 设置在界面的位置
        self.lbl_cam1 = tk.Label(self.window, text="CAM-1", bg='gray')
        self.lbl_cam1.place(x=15, y=60, height=30, anchor='nw')
        self.entry_ip_var_camera1.place(x=75, y=60, width=100, height=30, anchor='nw')
        self.btn_cam1_set_master = tk.Button(self.window, text='Set as Master', state=tk.DISABLED, command=self.socket_set_cam1_as_master)
        self.btn_cam1_set_master.place(x=280, y=60, width=95, height=30, anchor='ne')
        self.entry_master_status1.place(x=300, y=60, width=60, height=30, anchor='nw')
        self.btn_cam1_mount = tk.Button(self.window, text='Mount Linux Host', state=tk.DISABLED, command=self.socket_set_linuxhost_bindir_cam1)
        self.btn_cam1_mount.place(x=500, y=60, width=120, height=30, anchor='ne')
        self.bindir_camera1.place(x=520, y=60, width=350, height=30, anchor='nw')

        self.lbl_cam2 = tk.Label(self.window, text="CAM-2", bg='gray')
        self.lbl_cam2.place(x=15, y=95, height=30, anchor='nw')
        self.entry_ip_var_camera2.place(x=75, y=95, width=100, height=30, anchor='nw')
        self.btn_cam2_set_master = tk.Button(self.window, text='Set as Master', state=tk.DISABLED, command=self.socket_set_cam2_as_master)
        self.btn_cam2_set_master.place(x=280, y=95, width=95, height=30, anchor='ne')
        self.entry_master_status2.place(x=300, y=95, width=60, height=30, anchor='nw')
        self.btn_cam2_mount = tk.Button(self.window, text='Mount Linux Host', state=tk.DISABLED, command=self.socket_find_camera)
        self.btn_cam2_mount.place(x=500, y=95, width=120, height=30, anchor='ne')
        self.bindir_camera2.place(x=520, y=95, width=350, height=30, anchor='nw')

        self.lbl_cam3 = tk.Label(self.window, text="CAM-3", bg='gray')
        self.lbl_cam3.place(x=15, y=130, height=30, anchor='nw')
        self.entry_ip_var_camera3.place(x=75, y=130, width=100, height=30, anchor='nw')
        self.btn_cam3_set_master = tk.Button(self.window, text='Set as Master', state=tk.DISABLED, command=self.socket_set_cam3_as_master)
        self.btn_cam3_set_master.place(x=280, y=130, width=95, height=30, anchor='ne')
        self.entry_master_status3.place(x=300, y=130, width=60, height=30, anchor='nw')
        self.btn_cam3_mount = tk.Button(self.window, text='Mount Linux Host', state=tk.DISABLED, command=self.socket_find_camera)
        self.btn_cam3_mount.place(x=500, y=130, width=120, height=30, anchor='ne')
        self.bindir_camera3.place(x=520, y=130, width=350, height=30, anchor='nw')

        self.lbl_cam4 = tk.Label(self.window, text="CAM-4", bg='gray')
        self.lbl_cam4.place(x=15, y=165, height=30, anchor='nw')
        self.entry_ip_var_camera4.place(x=75, y=165, width=100, height=30, anchor='nw')
        self.btn_cam4_set_master = tk.Button(self.window, text='Set as Master', state=tk.DISABLED, command=self.socket_set_cam4_as_master)
        self.btn_cam4_set_master.place(x=280, y=165, width=95, height=30, anchor='ne')
        self.entry_master_status4.place(x=300, y=165, width=60, height=30, anchor='nw')
        self.btn_cam4_mount = tk.Button(self.window, text='Mount Linux Host', state=tk.DISABLED, command=self.socket_find_camera)
        self.btn_cam4_mount.place(x=500, y=165, width=120, height=30, anchor='ne')
        self.bindir_camera4.place(x=520, y=165, width=350, height=30, anchor='nw')

        self.lbl_master = tk.Label(self.window, text="MASTER", bg='gray')
        self.lbl_master.place(x=10, y=200, height=30, anchor='nw')
        self.entry_ip_var_master.place(x=75, y=200, width=100, height=30, anchor='nw')

        ######################################################################
        # Linux host挂载目录
        self.btn_host_dir = tk.Button(self.window, text="Linux Host Dir", bg='gray', state=tk.DISABLED, command=self.thread_socket_set_LinuxHostBinDir)
        self.btn_host_dir.place(x=500, y=200, width=120, height=30, anchor='ne')
        self.linux_host_bindir          = tk.StringVar()
        self.entry_linux_host_bindir    = tk.Entry(self.window, textvariable=self.linux_host_bindir)
        self.entry_linux_host_bindir.insert(0, "Click 'Linux Host Dir' to choose a host mount point")
        self.entry_linux_host_bindir.configure(state=tk.DISABLED)
        self.entry_linux_host_bindir.place(x=520, y=200, width=350, height=30, anchor='nw')

        # fits 文件状态
        self.fits1_is_rdy = False
        self.fits2_is_rdy = False
        self.fits3_is_rdy = False
        self.fits4_is_rdy = False


        ######################################################################
        # 添加fits图像输出主目录。暂时使用tmp目录
        # self.btn_set_fits_path  = tk.Button(self.window, text='Fits Output Path', command=self.thread_set_fits_path)
        # self.fits_path          = tk.StringVar()
        # self.entry_fits_path    = tk.Entry(self.window, textvariable=self.fits_path)
        # self.entry_fits_path.insert(0, 'enter fits output path here!')
        # self.entry_fits_path.place(x=700, y=250, anchor='nw')

        ######################################################################
        # 添加socket连接确认按钮
        tk.Button(self.window, text='Check Host Connection', command=self.socket_check_host_connection).place(x=20, y=275)

        # 添加socket服务端启动按钮
        self.socket_server_ip_var = tk.StringVar()
        self.socket_server_ip_var.set('192.168.0.104')
        self.entry_socket_server_ip = tk.Entry(self.window, textvariable=self.socket_server_ip_var)
        self.entry_socket_server_ip.place(x=550, y=275, width=110, height=30)

        self.socket_server_port_var = tk.StringVar()
        self.socket_server_port_var.set('8888')
        self.entry_socket_server_port = tk.Entry(self.window, textvariable=self.socket_server_port_var)
        self.entry_socket_server_port.place(x=665, y=275, width=50, height=30)
        
        self.socket_server_is_running = False
        self.socket_server = socket.socket()
        self.btn_socket_server_start = tk.Button(self.window, text='Start Socket Server', command=self.thread_start_socket_server, bg='gold')
        self.btn_socket_server_start.place(width=150, x=725, y=275, height=30, anchor='nw')
        self.ThreadClientCount = 0

        # 添加socket服务端终止按钮
        self.abort_socket_server = False
        # self.btn_socket_server_end = tk.Button(self.window, text='End Socket Server', command=self.end_socket_server).place(width=150, x=700, y=300)


        ######################################################################
        # 添加曝光/终止曝光按钮
        # tk.Button(self.window, text='Exposure', command=self.socket_exposure).place(x=20, y=350)
        self.var_expt = tk.StringVar()
        self.var_expt.set('0')
        self.entry_expt = tk.Entry(self.window, textvariable=self.var_expt)
        self.entry_expt.place(x=20, y=350, width=100, height=30, anchor='nw')

        self.btn_set_expt = tk.Button(self.window, text='Set ExpTime', command=self.socket_set_exposure_time)
        self.btn_set_expt.place(x=150, y=350, width=85, height=30)
        self.btn_exp = tk.Button(self.window, text='Exposure (Readout)', command=self.socket_master_exposure)
        self.btn_exp.place(x=280, y=350, height=30)
        # self.btn_exp.configure(bg='green')
        self.btn_exp.configure(state=tk.DISABLED, bg='gray')

        self.btn_abort_exp = tk.Button(self.window, text='AbortExp', height=30, command=self.socket_abort)
        self.btn_abort_exp.place(x=450, y=350, height=30)
        self.btn_abort_exp.configure(state=tk.DISABLED, bg='gray')

        ######################################################################
        # 添加退出按钮
        # self.btn_exit = tk.Button(self.window, text='Exit', command=self.window.quit)
        self.btn_exit = tk.Button(self.window, text='Exit', bg='red', command=self.exit_gui)
        self.btn_exit.place(x=820, y=350, anchor='nw')

        ######################################################################
        # 添加日志输出对话框
        # self.text_widget = tk.Text(self.window).place(x=20, y=200)
        self.text_widget = tk.Text(self.window)
        self.text_widget.pack(side='bottom', fill='x')
        self.log_handler = LogHandler(self.text_widget)
        # 创建日志记录器
        self.logger = logging.getLogger()
        self.logger.addHandler(self.log_handler)
        self.logger.setLevel(logging.INFO)
        self.logger.info('Start logging ...')

        self.fits_basename = ''

        #######################
        # fits头文件
        self.header_ccd1 = FitsHeader(1)
        self.header_ccd2 = FitsHeader(2)
        self.header_ccd3 = FitsHeader(3)
        self.header_ccd4 = FitsHeader(4)

        self.header1_rdy = False
        self.header2_rdy = False
        self.header3_rdy = False
        self.header4_rdy = False

        ################################
        # 设置性能测试时的FITS文件的输出目录
        self.main_fits_outdir = None
        self.entry_fits_outdir_var = tk.StringVar()
        self.entry_fits_outdir_var.set('Eneter fits output dir here!')
        self.entry_fits_outdir = tk.Entry(self.window, textvariable=self.entry_fits_outdir_var)
        self.entry_fits_outdir.place(x=200, y=400, height=30, width=600)
        self.btn_fits_outdir = tk.Button(self.window, text='Choose main fits outdir', command=self.thread_set_main_fits_outdir)
        self.btn_fits_outdir.place(x=10, y=400, width=180, height=30)

        self.current_fits_outdir1 = None # 用于性能测试时对不同项目类型的fits文件归类，默认值是self.fits_outdir
        self.entry_current_fits_outdir1_var = tk.StringVar()
        self.entry_current_fits_outdir1 = tk.Entry(self.window, textvariable=self.entry_current_fits_outdir1_var)
        self.entry_current_fits_outdir1.place(x=200, y=435, height=30, width=600)
        self.lbl_current_fits_outdir1 = tk.Label(self.window, text='CCD1 fits outdir', bg='grey')
        self.lbl_current_fits_outdir1.place(x=10, y=435, width=180, height=30)

        self.current_fits_outdir2 = None # 用于性能测试时对不同项目类型的fits文件归类，默认值是self.fits_outdir
        self.entry_current_fits_outdir2_var = tk.StringVar()
        self.entry_current_fits_outdir2 = tk.Entry(self.window, textvariable=self.entry_current_fits_outdir2_var)
        self.entry_current_fits_outdir2.place(x=200, y=470, height=30, width=600)
        self.lbl_current_fits_outdir2 = tk.Label(self.window, text='CCD2 fits outdir', bg='grey')
        self.lbl_current_fits_outdir2.place(x=10, y=470, width=180, height=30)

        self.current_fits_outdir3 = None # 用于性能测试时对不同项目类型的fits文件归类，默认值是self.fits_outdir
        self.entry_current_fits_outdir3_var = tk.StringVar()
        self.entry_current_fits_outdir3 = tk.Entry(self.window, textvariable=self.entry_current_fits_outdir3_var)
        self.entry_current_fits_outdir3.place(x=200, y=505, height=30, width=600)
        self.lbl_current_fits_outdir3 = tk.Label(self.window, text='CCD3 fits outdir', bg='grey')
        self.lbl_current_fits_outdir3.place(x=10, y=505, width=180, height=30)

        self.current_fits_outdir4 = None # 用于性能测试时对不同项目类型的fits文件归类，默认值是self.fits_outdir
        self.entry_current_fits_outdir4_var = tk.StringVar()
        self.entry_current_fits_outdir4 = tk.Entry(self.window, textvariable=self.entry_current_fits_outdir4_var)
        self.entry_current_fits_outdir4.place(x=200, y=540, height=30, width=600)
        self.lbl_current_fits_outdir4 = tk.Label(self.window, text='CCD4 fits outdir', bg='grey')
        self.lbl_current_fits_outdir4.place(x=10, y=540, width=180, height=30)

##############################################################################################################

    def exit_gui(self):
        """
        退出PyMephisto-GUI程序,同时关闭socket服务端口.
        """
        if self.socket_server_is_running is True:
            self.socket_server.close()
            self.socket_server_is_running = False
        self.window.quit()

    def write_log(self, msg):
        """
        用于方便在程序任意位置处输出(调试或提示)日志
        """
        # logmsg = '{}:> {}'.format(datetime.datetime.now(), msg)
        # logmsg = '{}:> {}'.format(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), msg)
        # logmsg = '{}:> {}'.format(datetime.datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3], msg)
        logmsg = '{}:> {}'.format(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3], msg)
        self.logger.info(logmsg)

    def create_dir(self, dirname):
        """
        在self.fits_outdir目录下创建新的目录self.fits_outdir/dirname
        每次与labview通信,都会收到一个目录,然后判断该目录是否存在,不存在则创建,并更新self.current_fits_dir。
        拼接相机有4片CCD,为了数据处理的方便,将每片CCD的数据单独保存至一个目录下是必要的。
        """
        if self.main_fits_outdir is None:
            self.write_log('self.main_fits_outdir has NOT been set.')
            return
        
        if os.path.exists('{}/CCD1/{}'.format(self.main_fits_outdir, dirname)):
            self.write_log('{}/CCD1/{} already exists'.format(self.main_fits_outdir, dirname))
        else:
            cmd = 'mkdir -p {}/CCD1/{}'.format(self.main_fits_outdir, dirname)
            os.system(cmd)

        if os.path.exists('{}/CCD2/{}'.format(self.main_fits_outdir, dirname)):
            self.write_log('{}/CCD2/{} already exists'.format(self.main_fits_outdir, dirname))
        else:
            cmd = 'mkdir -p {}/CCD2/{}'.format(self.main_fits_outdir, dirname)
            os.system(cmd)

        if os.path.exists('{}/CCD3/{}'.format(self.main_fits_outdir, dirname)):
            self.write_log('{}/CCD3/{} already exists'.format(self.main_fits_outdir, dirname))            
        else:
            cmd = 'mkdir -p {}/CCD3/{}'.format(self.main_fits_outdir, dirname)
            os.system(cmd)

        if os.path.exists('{}/CCD4{}'.format(self.main_fits_outdir, dirname)):
            self.write_log('{}/CCD4/{} already exists'.format(self.main_fits_outdir, dirname))            
        else:
            cmd = 'mkdir -p {}/CCD4/{}'.format(self.main_fits_outdir, dirname)
            os.system(cmd)

        # self.current_fits_outdir = '{}/{}'.format(self.main_fits_outdir, dirname)
        self.current_fits_outdir1 = '{}/CCD1/{}'.format(self.main_fits_outdir, dirname)
        self.current_fits_outdir2 = '{}/CCD2/{}'.format(self.main_fits_outdir, dirname)
        self.current_fits_outdir3 = '{}/CCD3/{}'.format(self.main_fits_outdir, dirname)
        self.current_fits_outdir4 = '{}/CCD4/{}'.format(self.main_fits_outdir, dirname)

        self.entry_current_fits_outdir1_var.set(self.current_fits_outdir1)
        self.entry_current_fits_outdir2_var.set(self.current_fits_outdir2)
        self.entry_current_fits_outdir3_var.set(self.current_fits_outdir3)
        self.entry_current_fits_outdir4_var.set(self.current_fits_outdir4)

        self.write_log('set current_fits_outdir1: {}'.format( self.current_fits_outdir1))
        self.write_log('set current_fits_outdir2: {}'.format( self.current_fits_outdir2))
        self.write_log('set current_fits_outdir3: {}'.format( self.current_fits_outdir3))
        self.write_log('set current_fits_outdir4: {}'.format( self.current_fits_outdir4))

    ########################################################################################
    # 以下是与各按钮对应的线程函数
    def thread_find_camera(self):
        camera_found = 0 # DONOT forget to reset it to zero
        self.camera_ips = []
        self.ip_var_camera1.set('')
        self.ip_var_camera2.set('')
        self.ip_var_camera3.set('')
        self.ip_var_camera4.set('')
        self.ip_var_master.set('')

        addr = self.host_ip_var.get()
        args = "".join(addr)
        ip_pre = '.'.join(args.split('.')[:-1])

        self.write_log('Start searching Camera in local network {}.xxx'.format(ip_pre))

        ips = [ip_pre+'.{}'.format(i) for i in range(100,256)]
        # print('ips: {}'.format(ips))
        
        for ip in ips:
            self.write_log('trying IP: {}'.format(ip))
            try:
                # local_sock = socket.socket()
                local_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                local_sock.settimeout(0.02)                                     # 这个非常重要
                local_sock.connect( (ip, int(self.host_port_var.get())) )

                cmd = 'whoareyou'
                local_sock.send(cmd.encode('utf-8'))

                while True:
                    recv = local_sock.recv(1024)
                    recv_str = recv.decode('utf-8')
                    if recv_str.find('blue1') != -1:  # TODO: 这里以后要进行修改
                        local_sock.send('exit'.encode('utf-8'))
                        camera_found += 1 
#                        self.ip_var.set(ip)
                        self.camera_ips.append(ip)
                        break

                    if recv_str.find('blue2') != -1:  # TODO: 这里以后要进行修改
                        local_sock.send('exit'.encode('utf-8'))
                        camera_found += 1
#                        self.ip_var.set(ip)
                        self.camera_ips.append(ip)
                        break

                    if recv_str.find('blue3') != -1:  # TODO: 这里以后要进行修改
                        local_sock.send('exit'.encode('utf-8'))
                        camera_found += 1
#                        self.ip_var.set(ip)
                        self.camera_ips.append(ip)
                        break

                    if recv_str.find('blue4') != -1:  # TODO: 这里以后要进行修改
                        local_sock.send('exit'.encode('utf-8'))
                        camera_found += 1
#                        self.ip_var.set(ip)
                        self.camera_ips.append(ip)
                        break

                    # if len(self.camera_ips) == 4:
                    #     break


                local_sock.close()
                time.sleep(0.1)
                

            except:
                pass

            if camera_found != 0:
                break

        if camera_found == 0:
            self.write_log('*****************************')
            self.write_log('****   No camera found! *****')
            self.write_log('**** CHECK YOUR INPUT IP ****')
            self.write_log('*****************************')
        else:
            self.write_log('Found the Camera:')
            for i in range(len(self.camera_ips)):
                self.write_log('CAM{} IP: {}'.format(i+1, self.camera_ips[i]))

#            self.camera_ips = ['192.168.0.109','192.168.0.106','192.168.0.110', '192.168.0.108']
            self.camera_ips = ['192.168.0.106','192.168.0.109', '192.168.0.108','192.168.0.110']
            
            self.write_log('self.camera_found : {}'.format(camera_found))

            self.btn_search_cam.configure(bg='lime')
            self.btn_reset_config.configure(bg='cornflowerblue')

            self.btn_host_dir.configure(state=tk.NORMAL)
            self.btn_exit.configure(bg='brown')

            self.btn_exp.configure(state=tk.NORMAL, bg='green')


            if len(self.camera_ips) == 1:
                self.ip_var_camera1.set(self.camera_ips[0])
                self.btn_cam1_set_master.configure(state=tk.NORMAL)
                self.btn_cam1_mount.configure(state=tk.NORMAL)
                self.lbl_cam1.configure(bg='lime')
            elif len(self.camera_ips) == 2:
                self.ip_var_camera1.set(self.camera_ips[0])
                self.btn_cam1_set_master.configure(state=tk.NORMAL)
                self.btn_cam1_mount.configure(state=tk.NORMAL)
                self.lbl_cam1.configure(bg='lime')

                self.ip_var_camera2.set(self.camera_ips[1])
                self.btn_cam2_set_master.configure(state=tk.NORMAL)
                self.btn_cam2_mount.configure(state=tk.NORMAL)
                self.lbl_cam2.configure(bg='lime')
            elif len(self.camera_ips) == 3:
                self.ip_var_camera1.set(self.camera_ips[0])
                self.btn_cam1_set_master.configure(state=tk.NORMAL)
                self.btn_cam1_mount.configure(state=tk.NORMAL)
                self.lbl_cam1.configure(bg='lime')

                self.ip_var_camera2.set(self.camera_ips[1])
                self.btn_cam2_set_master.configure(state=tk.NORMAL)
                self.btn_cam2_mount.configure(state=tk.NORMAL)
                self.lbl_cam2.configure(bg='lime')

                self.ip_var_camera3.set(self.camera_ips[2])
                self.btn_cam3_set_master.configure(state=tk.NORMAL)
                self.btn_cam3_mount.configure(state=tk.NORMAL)
                self.lbl_cam3.configure(bg='lime')

            elif len(self.camera_ips) == 4:
                self.ip_var_camera1.set(self.camera_ips[0])
#                self.btn_cam1_set_master.configure(state=tk.NORMAL)
                self.btn_cam1_mount.configure(state=tk.NORMAL)
                self.lbl_cam1.configure(bg='lime')
                
                self.ip_var_camera2.set(self.camera_ips[1])
                self.btn_cam2_set_master.configure(state=tk.NORMAL)
                self.btn_cam2_mount.configure(state=tk.NORMAL)
                self.lbl_cam2.configure(bg='lime')
                
                self.ip_var_camera3.set(self.camera_ips[2])
#                self.btn_cam3_set_master.configure(state=tk.NORMAL)
                self.btn_cam3_mount.configure(state=tk.NORMAL)
                self.lbl_cam3.configure(bg='lime')

                self.ip_var_camera4.set(self.camera_ips[3])
#                self.btn_cam4_set_master.configure(state=tk.NORMAL)
                self.btn_cam4_mount.configure(state=tk.NORMAL)
                self.lbl_cam4.configure(bg='lime')


    def reset_config(self):
        self.write_log('Start clearing configurations ...')
        self.btn_search_cam.configure(bg='gold')
        self.ip_var_camera1.set('')
        self.ip_var_camera2.set('')
        self.ip_var_camera3.set('')
        self.ip_var_camera4.set('')
        self.ip_var_master.set('')
        self.ip_var_master_is_set = False

        self.role_var_camera1.set('')
        self.role_var_camera2.set('')
        self.role_var_camera3.set('')
        self.role_var_camera4.set('')

        self.lbl_cam1.configure(bg='gray')
        self.lbl_cam2.configure(bg='gray')
        self.lbl_cam3.configure(bg='gray')
        self.lbl_cam4.configure(bg='gray')

        self.btn_cam1_set_master.configure(state=tk.DISABLED)
        self.btn_cam2_set_master.configure(state=tk.DISABLED)
        self.btn_cam3_set_master.configure(state=tk.DISABLED)
        self.btn_cam4_set_master.configure(state=tk.DISABLED)
        self.lbl_master.configure(bg='gray')

        self.btn_cam1_mount.configure(state=tk.DISABLED)
        self.btn_cam2_mount.configure(state=tk.DISABLED)
        self.btn_cam3_mount.configure(state=tk.DISABLED)
        self.btn_cam4_mount.configure(state=tk.DISABLED)

        self.linux_host_bindir.set("")
        self.btn_host_dir.configure(bg='gray')
        self.btn_host_dir.configure(state=tk.DISABLED)
        self.entry_linux_host_bindir.configure(state=tk.DISABLED)

        self.btn_exp.configure(state=tk.DISABLED, bg='gray')

        self.ip_var_master_is_set = False

        self.btn_reset_config.configure(bg='gray')
        self.write_log('Finished clearing configurations')


    def thread_check_host_connection(self):
        try:
            cmd = 'check_connection'
            local_sock = socket.socket()
            # local_sock.settimeout(0.02)                                         # 这个非常重要
            local_sock.connect((self.host_ip_var.get(), int(self.host_port_var.get())))
            local_sock.send(cmd.encode('utf-8'))

            while True:
                recv = local_sock.recv(1024)
                recv_str = recv.decode('utf-8')

                if recv_str.find('check_connection_ok') != -1:
                    local_sock.send('exit'.encode('utf-8'))
                    self.write_log('host connection is OK')
                    break
        except:
            self.write_log('socket connecttion failed.')


    def thread_socket_command(self,thread_name, cmd):
        try:
            local_sock = socket.socket()
            # local_sock.settimeout(0.02)                                         # 这个非常重要，但是放在这里会出问题
            local_sock.connect((self.host_ip_var.get(), int(self.host_port_var.get())))
            local_sock.send(cmd.encode('utf-8'))
            self.write_log('send cmd: {}'.format(cmd))

            while True:
                recv = local_sock.recv(1024)
                recv_str = recv.decode('utf-8')
                if recv_str.find(cmd+'_ok') != -1:
                    local_sock.send('exit'.encode('utf-8'))
                    self.write_log('close socket({}) on thread({})'.format(local_sock.fileno(), _thread.get_native_id()))
                    break
        except:
            self.write_log('socket connecttion failed.')


    def thread_socket_ip_command(self, ip='', thread_name='', cmd=''):
        try:
            local_sock = socket.socket()
            # local_sock.settimeout(0.02)                           # 这个非常重要，但是放在这里会出问题
            local_sock.connect((ip, int(self.host_port_var.get())))      # 遇到一个奇怪的问题，即使IP为空，也能正常连接至测试时的IP
            local_sock.send(cmd.encode('utf-8'))

            if cmd.find('exposure') != -1:
                self.btn_exp.configure(state=tk.DISABLED, bg='gray')

            wait_cnt = 0
            while True:
                recv = local_sock.recv(1024)
                recv_str = recv.decode('utf-8')
                # for debug 
                # self.write_log('> recieved: -{}-'.format(recv_str))
                # self.write_log('> recv_str.find(cmd+\'_ok\') = {}'.format(recv_str.find(cmd+'_ok')))

                # if recv_str.find(cmd+'_ok') != -1:
                if recv_str.find(cmd+'_ok') != -1:
                    """
                    @2024-10-07:
                    在这里犯了一个非常低级的SB错误:如果给服务端发送的指令是复合的,类似'set_exp_t|3.0'这种,服务端按照预期的方式
                    进行解析并执行了操作,但是返回的消息却是set_exp_t_ok.然而在这里利用recv_str.find(cmd+'_ok')进行比较的时候,
                    cmd+'_ok'中的内容实际是'set_exp_t|3.0_ok',因此recv_str.find(cmd+'_ok')的返回值必然是-1,这里后续的代码
                    自然不会执行,服务端那边收不到exit指令,线程函数自然不会退出了!!!
                    为了保险起见,服务端在完成一个指令后,返回的信息全部用recvbuf+'_ok'这种组合方式进行.
                    """
                    # local_sock.send(('exit|\n').encode('utf-8'))
                    local_sock.send('exit'.encode('utf-8'))
                    # self.write_log('send exit to server')
                    # self.write_log('{} on thread({}) is executed'.format(thread_name, _thread.get_native_id()))

                    if cmd.find('exposure') != -1:
                        self.btn_exp.configure(state=tk.NORMAL, bg='green')

                    break

                # wait_cnt += 1
                # time.sleep(1)
                # if wait_cnt > 5:
                #     self.write_log('thread({}) exit due to long time no repsponse from socket server...'.format(_thread.get_native_id()))
                #     break
        except:
            self.write_log('socket connecttion failed.')

    def thread_socket_ip_exposure(self, ip='', thread_name='', cmd=''):
        """
        曝光时需要将按钮禁用,这个与其他指令有所不同,因此单独定义这个专用的函数
        """
        try:
            local_sock = socket.socket()
            # local_sock.settimeout(0.02)                           # 这个非常重要，但是放在这里会出问题
            local_sock.connect((ip, int(self.host_port_var.get())))      # 遇到一个奇怪的问题，即使IP为空，也能正常连接至测试时的IP
            local_sock.send(cmd.encode('utf-8'))
            self.write_log('expsoure begins')
            self.btn_exp.configure(state=tk.DISABLED, bg='gray')
            self.btn_abort_exp.configure(state=tk.NORMAL, bg='red')

            wait_cnt = 0
            while True:
                recv = local_sock.recv(1024)
                recv_str = recv.decode('utf-8')
                if recv_str.find(cmd+'_ok') != -1:
                    local_sock.send('exit'.encode('utf-8'))
                    self.write_log('expsoure ends')
                    self.btn_exp.configure(state=tk.NORMAL, bg='green')
                    self.btn_abort_exp.configure(state=tk.DISABLED, bg='gray')
                    break
        except:
            self.write_log('socket connecttion failed.')

#   设置各相机的输出文件名
    def thread_socket_set_binfilename(self):
        pass

#   设置Linux主机挂载目录，存放bin文件
    def thread_socket_set_LinuxHostBinDir(self):
        self.entry_linux_host_bindir.configure(state=tk.NORMAL)
        self.write_log('updating Linux Host Dir...')
        self.linux_host_bindir.set(filedialog.askdirectory())
        self.write_log('new host dir: {}'.format(self.linux_host_bindir.get()))
        self.btn_host_dir.configure(bg='lime')

#   设置fits文件输出路径
    def thread_set_main_fits_outdir(self):

        self.write_log('setting fits outdir ...')

        tmp_outdir = str(filedialog.askdirectory())
        if tmp_outdir == '()':
            self.main_fits_outdir = os.getcwd()
            self.write_log('***************************************************************')
            self.write_log('NO outdir been chosen, set to: {}'.format(self.main_fits_outdir))
            self.write_log('***************************************************************\n')
            return
        
        self.main_fits_outdir = tmp_outdir

        self.entry_fits_outdir_var.set(self.main_fits_outdir)

        self.current_fits_outdir1 = self.main_fits_outdir + '/CCD1'
        self.entry_current_fits_outdir1_var.set(self.current_fits_outdir1)
        self.btn_fits_outdir.configure(bg='lime')

        self.current_fits_outdir2 = self.main_fits_outdir + '/CCD2'
        self.entry_current_fits_outdir2_var.set(self.current_fits_outdir2)
        self.btn_fits_outdir.configure(bg='lime')

        self.current_fits_outdir3 = self.main_fits_outdir + '/CCD3'
        self.entry_current_fits_outdir3_var.set(self.current_fits_outdir3)
        self.btn_fits_outdir.configure(bg='lime')

        self.current_fits_outdir4 = self.main_fits_outdir + '/CCD4'
        self.entry_current_fits_outdir4_var.set(self.current_fits_outdir4)
        self.btn_fits_outdir.configure(bg='lime')

        self.write_log('main fits outdir: {}'.format(self.main_fits_outdir))
        self.write_log('current fits outdir: {}'.format(self.current_fits_outdir1))
        self.write_log('current fits outdir: {}'.format(self.current_fits_outdir2))
        self.write_log('current fits outdir: {}'.format(self.current_fits_outdir3))
        self.write_log('current fits outdir: {}'.format(self.current_fits_outdir4))

        self.lbl_current_fits_outdir1.configure(bg='lightgreen')
        self.lbl_current_fits_outdir2.configure(bg='lightgreen')
        self.lbl_current_fits_outdir3.configure(bg='lightgreen')
        self.lbl_current_fits_outdir4.configure(bg='lightgreen')

#   设置master/slave
    def thread_socket_set_cam1_as_master(self):
        if len(self.ip_var_camera1.get()) > 0:
            # send command 'set_as_master'
            self.thread_socket_ip_command(self.ip_var_camera1.get(), 'thread_socket_set_cam1_as_master', 'set_as_master')
            self.write_log('set CAM-1 as master')
            self.role_var_camera1.set('Master')
            self.ip_var_master.set(self.ip_var_camera1.get())
            self.lbl_master.configure(bg='lime')
            self.ip_var_master_is_set = True
        
        if len(self.ip_var_camera2.get()) > 0:
            # send command 'set_as_slave'
            self.thread_socket_ip_command(self.ip_var_camera2.get(), 'thread_socket_set_cam1_as_master', 'set_as_slave')
            self.write_log('set CAM-2 as slave')
            self.role_var_camera2.set('Slave')

        if len(self.ip_var_camera3.get()) > 0:
            # send command 'set_as_slave'
            self.thread_socket_ip_command(self.ip_var_camera3.get(), 'thread_socket_set_cam1_as_master', 'set_as_slave')
            self.write_log('set CAM-3 as slave')
            self.role_var_camera3.set('Slave')

        if len(self.ip_var_camera4.get()) > 0:
            # send command 'set_as_slave'
            self.thread_socket_ip_command(self.ip_var_camera4.get(), 'thread_socket_set_cam1_as_master', 'set_as_slave')
            self.write_log('set CAM-4 as slave')
            self.role_var_camera4.set('Slave')

    def thread_socket_set_cam2_as_master(self):
        if len(self.ip_var_camera2.get()) > 0:
            # send command 'set_as_master'
            self.thread_socket_ip_command(self.ip_var_camera2.get(), 'thread_socket_set_cam2_as_master', 'set_as_master')
            self.write_log('set CAM-2 as master')
            self.role_var_camera2.set('Master')
            self.ip_var_master.set(self.ip_var_camera2.get())
            self.lbl_master.configure(bg='lime')
            self.ip_var_master_is_set = True
        
        if len(self.ip_var_camera1.get()) > 0:
            # send command 'set_as_slave'
            self.thread_socket_ip_command(self.ip_var_camera1.get(), 'thread_socket_set_cam2_as_master', 'set_as_slave')
            self.write_log('set CAM-1 as slave')
            self.role_var_camera1.set('Slave')
        
        if len(self.ip_var_camera3.get()) > 0:
            # send command 'set_as_slave'
            self.thread_socket_ip_command(self.ip_var_camera3.get(), 'thread_socket_set_cam2_as_master', 'set_as_slave')
            self.write_log('set CAM-3 as slave')
            self.role_var_camera3.set('Slave')

        if len(self.ip_var_camera4.get()) > 0:
            # send command 'set_as_slave'
            self.thread_socket_ip_command(self.ip_var_camera4.get(), 'thread_socket_set_cam2_as_master', 'set_as_slave')
            self.write_log('set CAM-4 as slave')
            self.role_var_camera4.set('Slave')

    def thread_socket_set_cam3_as_master(self):
        if len(self.ip_var_camera3.get()) > 0:
            # send command 'set_as_master'
            self.thread_socket_ip_command(self.ip_var_camera3.get(), 'thread_socket_set_cam3_as_master', 'set_as_master')
            self.write_log('set CAM-3 as master')
            self.role_var_camera3.set('Master')
            self.ip_var_master.set(self.ip_var_camera3.get())
            self.lbl_master.configure(bg='lime')
            self.ip_var_master_is_set = True

        if len(self.ip_var_camera1.get()) > 0:
            # send command 'set_as_slave'
            self.thread_socket_ip_command(self.ip_var_camera1.get(), 'thread_socket_set_cam3_as_master', 'set_as_slave')
            self.write_log('set CAM-1 as slave')
            self.role_var_camera1.set('Slave')

        if len(self.ip_var_camera2.get()) > 0:
            # send command 'set_as_slave'
            self.thread_socket_ip_command(self.ip_var_camera2.get(), 'thread_socket_set_cam3_as_master', 'set_as_slave')
            self.write_log('set CAM-2 as slave')
            self.role_var_camera2.set('Slave')


        if len(self.ip_var_camera4.get()) > 0:
            # send command 'set_as_slave'
            self.thread_socket_ip_command(self.ip_var_camera4.get(), 'thread_socket_set_cam3_as_master', 'set_as_slave')
            self.write_log('set CAM-4 as slave')
            self.role_var_camera3.set('Slave')


    def thread_socket_set_cam4_as_master(self):
        if len(self.ip_var_camera4.get()) > 0:
            # send command 'set_as_master'
            self.thread_socket_ip_command(self.ip_var_camera4.get(), 'thread_socket_set_cam4_as_master', 'set_as_master')
            self.write_log('set CAM-4 as master')
            self.role_var_camera4.set('Master')
            self.ip_var_master.set(self.ip_var_camera4.get())
            self.lbl_master.configure(bg='lime')
            self.ip_var_master_is_set = True

        if len(self.ip_var_camera1.get()) > 0:
            # send command 'set_as_slave'
            self.thread_socket_ip_command(self.ip_var_camera1.get(), 'thread_socket_set_cam4_as_master', 'set_as_slave')
            self.write_log('set CAM-1 as slave')
            self.role_var_camera1.set('Slave')

        if len(self.ip_var_camera2.get()) > 0:
            # send command 'set_as_slave'
            self.thread_socket_ip_command(self.ip_var_camera2.get(), 'thread_socket_set_cam4_as_master', 'set_as_slave')
            self.write_log('set CAM-2 as slave')
            self.role_var_camera2.set('Slave')

        if len(self.ip_var_camera3.get()) > 0:
            # send command 'set_as_slave'
            self.thread_socket_ip_command(self.ip_var_camera3.get(), 'thread_socket_set_cam4_as_master', 'set_as_slave')
            self.write_log('set CAM-3 as slave')
            self.role_var_camera3.set('Slave')

    def thread_socket_set_host_dir_cam(self):
        pass


    def thread_socket_bin2fits(self, binfname, chip_idx=1):
         while True:
            if os.path.isfile(binfname+'_ok'):
            
#                cmd = 'bin2fits ' + binfname + ' ' + binfname + '.fits'
#                print(cmd)
#                os.system(cmd)
#                self.write_log(cmd)
                
                fitsname = binfname + '.fits'
                bin2fits(binfname=binfname, outfits=fitsname)
                self.write_log('generated {}'.format(fitsname))
                
                if chip_idx == 1:
                    self.fits1_is_rdy = True
                if chip_idx == 2:
                    self.fits2_is_rdy = True
                if chip_idx == 3:
                    self.fits3_is_rdy = True
                if chip_idx == 4:
                    self.fits4_is_rdy = True

                time.sleep(0.1)
                cmd = 'rm '+binfname+'_ok'
                os.system(cmd)
#                cmd = 'rm '+binfname
#                os.system(cmd)
                break
            time.sleep(0.5)

    def thread_bin2fits(self, binfname, fitsfname, chip_idx=1):
        """
          调用bin2fits.py中的bin2fits函数进行转换
          添加头文件@20241125
        """

        header_info = None
        if chip_idx == 1:
            header_info = self.header_ccd1.header
        
        if chip_idx == 2:
            header_info = self.header_ccd2.header
            
        if chip_idx == 3:
            header_info = self.header_ccd3.header
            
        if chip_idx == 4:
            header_info = self.header_ccd4.header

        while True:
            if os.path.isfile(binfname+'_ok'):
                # prepare fits_path+fitsfname

                bin2fits(binfname=binfname, outfits=fitsfname,header_info=header_info)
                self.write_log('generated {}'.format(fitsfname))
                time.sleep(0.5)
                cmd = 'rm '+binfname+'_ok'
                os.system(cmd)
#                cmd = 'rm '+binfname
#                os.system(cmd)
                break
            time.sleep(0.5)
            

    def thread_socket_openfits(self):
#        time.sleep(11)
#        cmd = 'ds9 ../tmp/*.fits &'
#        os.system(cmd)

        self.fits_wait_cnt = 0
        while True:
            """
            如果15s后，所有4个fits文件没有全部出现，那么就报错
            """
            if  self.fits1_is_rdy is True and \
                self.fits2_is_rdy is True and \
                self.fits3_is_rdy is True and \
                self.fits4_is_rdy:
                cmd = 'ds9 {} {} {} {}'.format('../tmp/blue1_tmp.bin.fits',\
                '../tmp/blue2_tmp.bin.fits','../tmp/blue3_tmp.bin.fits',\
                '../tmp/blue4_tmp.bin.fits')
                os.system(cmd)
                break
                
            time.sleep(1)
            self.fits_wait_cnt += 1
            if self.fits_wait_cnt >= 15:
                if self.fits1_is_rdy is False:
                    self.write_log('fits1 is not generated correctly')
                
                if self.fits2_is_rdy is False:
                    self.write_log('fits2 is not generated correctly')

                if self.fits3_is_rdy is False:
                    self.write_log('fits3 is not generated correctly')

                if self.fits4_is_rdy is False:
                    self.write_log('fits4 is not generated correctly')
                
                break
        
        self.fits1_is_rdy = False
        self.fits2_is_rdy = False
        self.fits3_is_rdy = False
        self.fits4_is_rdy = False

        
#   以下是与按钮绑定的函数

    def socket_exposure(self):
        _thread.start_new_thread( self.thread_socket_command, ('thread_command','exposure') )

    def socket_master_exposure(self):
        if self.ip_var_master_is_set:
            # _thread.start_new_thread( self.thread_socket_ip_command, (self.ip_var_master.get(), 'socket_master_exposure','exposure') )
            # _thread.start_new_thread( self.thread_socket_ip_exposure, (self.ip_var_master.get(), 'socket_master_exposure','readout') )

            _thread.start_new_thread( self.thread_socket_ip_exposure, (self.ip_var_camera2.get(), 'socket_master_exposure','readout') )
            _thread.start_new_thread( self.thread_socket_ip_exposure, (self.ip_var_camera1.get(), 'socket_master_exposure','readout') )
            _thread.start_new_thread( self.thread_socket_ip_exposure, (self.ip_var_camera3.get(), 'socket_master_exposure','readout') )
            _thread.start_new_thread( self.thread_socket_ip_exposure, (self.ip_var_camera4.get(), 'socket_master_exposure','readout') )

            self.fits1_is_rdy = False
            self.fits2_is_rdy = False
            self.fits3_is_rdy = False
            self.fits4_is_rdy = False

            _thread.start_new_thread( self.thread_socket_bin2fits, ('../tmp/blue1_tmp.bin',1) ) 
            _thread.start_new_thread( self.thread_socket_bin2fits, ('../tmp/blue2_tmp.bin',2) ) 
            _thread.start_new_thread( self.thread_socket_bin2fits, ('../tmp/blue3_tmp.bin',3) ) 
            _thread.start_new_thread( self.thread_socket_bin2fits, ('../tmp/blue4_tmp.bin',4) ) 

            _thread.start_new_thread( self.thread_socket_openfits, () ) 
        else:
            self.write_log('**** IP of master CAM has not been set ****')
            
    def socket_abort(self):
        # _thread.start_new_thread( self.thread_socket_command, ('thread_command','abort') )
        if self.ip_var_master_is_set:
            _thread.start_new_thread( self.thread_socket_ip_command, (self.ip_var_master.get(),'thread_command','abort') )
        else:
            self.write_log('**** IP of master CAM has not been set ****')

    def socket_check_host_connection(self):
        _thread.start_new_thread( self.thread_check_host_connection, () )

    def socket_find_camera(self):
        _thread.start_new_thread( self.thread_find_camera, () )

    def socket_set_exposure_time(self):
        # TODO: 该函数执行后,socket服务端子线程未退出.
        # 问题已经查明,是因为服务端返回的信息与cmd+'_ok'对不上
        if self.ip_var_master_is_set:
            cmd = 'set_exp_t'
            expt = float(self.var_expt.get())
            if expt > 0:
                cmd = cmd + '|{}'.format(expt)
            self.write_log('set expsoure time to {} seconds'.format(expt))
            self.write_log('[DEBUG] self.ip_var_master.get() is : {}'.format(self.ip_var_master.get()))
            _thread.start_new_thread( self.thread_socket_ip_command, (self.ip_var_master.get(), 'socket_set_exposure_time',cmd) )
        else:
            self.write_log('**** IP of master CAM has not been set ****')

    def socket_set_linuxhost_bindir_cam1(self):
        self.bindir_camera1_val.set(self.linux_host_bindir.get())
        ip = self.host_ip_var.get()
#        cmd = 'set_host_dir|{}:{}'.format(ip, self.bindir_camera1_val.get())
        cmd = 'set_host_dir|' + ip + ':' + self.bindir_camera1_val.get() + ' /mnt'
        self.write_log('sending cmd: {} to socket server'.format(cmd))
        _thread.start_new_thread( self.thread_socket_ip_command, (self.ip_var_master.get(), 'socket_set_linuxhost_bindir_cam1',cmd) )

    def socket_set_linuxhost_bindir_cam2(self):
        self.bindir_camera2_val.set(self.linux_host_bindir)
        cmd = 'set_host_dir' + '|' + self.bindir_camera2_val.get()
        _thread.start_new_thread( self.thread_socket_ip_command, (self.ip_var_master.get(), 'socket_set_linuxhost_bindir_cam2',cmd) )

    def socket_set_linuxhost_bindir_cam3(self):
        self.bindir_camera3_val.set(self.linux_host_bindir)
        cmd = 'set_host_dir' + '|' + self.bindir_camera3_val.get()
        _thread.start_new_thread( self.thread_socket_ip_command, (self.ip_var_master.get(), 'socket_set_linuxhost_bindir_cam3',cmd) )

    def socket_set_linuxhost_bindir_cam4(self):
        self.bindir_camera4_val.set(self.linux_host_bindir)
        cmd = 'set_host_dir' + '|' + self.bindir_camera4_val.get()
        _thread.start_new_thread( self.thread_socket_ip_command, (self.ip_var_master.get(), 'socket_set_linuxhost_bindir_cam4',cmd) )

    def socket_set_cam1_as_master(self):
        _thread.start_new_thread( self.thread_socket_set_cam1_as_master, () )

    def socket_set_cam2_as_master(self):
        _thread.start_new_thread( self.thread_socket_set_cam2_as_master, () )

    def socket_set_cam3_as_master(self):
        _thread.start_new_thread( self.thread_socket_set_cam3_as_master, () )

    def socket_set_cam4_as_master(self):
        _thread.start_new_thread( self.thread_socket_set_cam4_as_master, () )


    def start_socket_server(self):
        """
        启动Python-GUI程序的socket服务端，接收从labview发来的指令
        """
        if self.socket_server_is_running is True:
            self.write_log('self.socket_server is already running')
            return

        # IP = "192.168.0.104"
        IP = self.socket_server_ip_var.get()
        PORT= self.socket_server_port_var.get()
        self.socket_server.bind((IP, int(PORT)))

        self.socket_server.listen(1)
        # self.write_log(r"服务端已开始监听，正在等待客户端连接...")
        self.write_log(r"Socket server started, waits for client connection...")
        self.socket_server_is_running = True
        self.btn_socket_server_start.configure(bg='lime')

        while True:
            if self.abort_socket_server:
                break
            Client, address = self.socket_server.accept()
            _thread.start_new_thread(self.socket_client_handler, (Client,))
            self.ThreadClientCount += 1
            self.write_log('[Socket Client #] {}'.format(self.ThreadClientCount))

        self.socket_server.close()
        self.write_log('[Socket server is terminated.]')
        self.socket_server_is_running = False

    def thread_start_socket_server(self):
        # self.btn_socket_server_start.configure(bg='lime')
        _thread.start_new_thread(self.start_socket_server, ())


    def socket_client_handler(self,connection):
        connection.send(str.encode('Welcome to the Server'))
        # connection.send(str.encode(''))
        while True:
            data = connection.recv(MSG_LEN)
#            self.write_log('recieved: {}'.format(data.decode('utf-8')))

            if data.decode('utf-8').upper() == 'EXIT':
#                connection.send(str.encode('exit_ok'))
                self.write_log('EXIT_OK')
                break

            # reply = 'Server Says: ' + data.decode('utf-8')
            reply = ''
            if not data:
                break

            MSG = data.decode('utf-8')
            
            if MSG.find('$') != -1:
                self.write_log('received header mesg')
                self.header_ccd1.update_header(MSG)
                self.header_ccd2.update_header(MSG)
                self.header_ccd3.update_header(MSG)
                self.header_ccd4.update_header(MSG)
                
#                self.header_ccd1.print_header()

                self.write_log(MSG)

                self.header1_rdy = True
                self.header2_rdy = True
                self.header3_rdy = True
                self.header4_rdy = True

                reply = 'headerdone'
                time.sleep(0.01)
                connection.sendall(str.encode(reply))
#                self.write_log('#################  send headerdone to labview')
            elif MSG.find('@') != -1:
                self.write_log('received time tag, nothing need to do')

                reply = 'tagreceived'
                time.sleep(0.01)
                connection.sendall(str.encode(reply))
#                self.write_log('#################  send tagreceived to labview')
            elif MSG.find('&') != -1:
                """
                    创建输出目录的任务由Python-GUI来完成，这样最方便。
                    新创建的目录全部放在self.fits_outdir目录下面
                """
                self.write_log('received fits filepath')
#                path = '../tmp/' + MSG[1:]
#                cmd = 'mkdir -p ' + path
                # os.system(cmd)

                self.create_dir('{}'.format(MSG[1:]))
                self.write_log('created fits filepath: {}'.format(MSG[1:]))

                reply = 'filepathdone'
                time.sleep(0.01)
                connection.sendall(str.encode(reply))
#                self.write_log('#################  send filepathdone to labview')
            elif MSG.find('#') != -1:
                """
                    接收labview发来的fits文件名basename，随后在调用bin2fits的时候，将Blue{i}补充进来。
                """
                self.fits_basename = MSG[1:]
                self.write_log('received fits basename: {}'.format(self.fits_basename))

                reply = 'filenamedone'
                time.sleep(0.01)
                connection.sendall(str.encode(reply))
#                self.write_log('#################  send filenamedone to labview')
#            elif MSG.upper() == 'READOUT':
            elif MSG.upper() == 'READOUTX':
                reply = 'readoutdone'
                if self.ip_var_master_is_set:
                    self.write_log('> doing fake readout')
                   #time.sleep(1)
                    _thread.start_new_thread( self.thread_socket_ip_exposure, (self.ip_var_camera2.get(), 'socket_master_exposure','readout') )
                    _thread.start_new_thread( self.thread_socket_ip_exposure, (self.ip_var_camera1.get(), 'socket_master_exposure','readout') )
                    _thread.start_new_thread( self.thread_socket_ip_exposure, (self.ip_var_camera3.get(), 'socket_master_exposure','readout') )
                    _thread.start_new_thread( self.thread_socket_ip_exposure, (self.ip_var_camera4.get(), 'socket_master_exposure','readout') )
                    time.sleep(10)

                    #################################
                    # wait for *.bin files
                    # _thread.start_new_thread( self.thread_socket_bin2fits, ('../tmp/blue1_tmp.bin',) ) 
                    # _thread.start_new_thread( self.thread_socket_bin2fits, ('../tmp/blue2_tmp.bin',) ) 
                    # _thread.start_new_thread( self.thread_socket_bin2fits, ('../tmp/blue3_tmp.bin',) ) 
                    # _thread.start_new_thread( self.thread_socket_bin2fits, ('../tmp/blue4_tmp.bin',) ) 

                    tmp_linux_host_bindir = self.linux_host_bindir.get()
#                    bin1 = '{}/blue1_tmp.bin'.format(tmp_linux_host_bindir)
#                    bin2 = '{}/blue2_tmp.bin'.format(tmp_linux_host_bindir)
#                    bin3 = '{}/blue3_tmp.bin'.format(tmp_linux_host_bindir)
#                    bin4 = '{}/blue4_tmp.bin'.format(tmp_linux_host_bindir)
                    
                    bin1 = '../tmp/blue1_tmp.bin'
                    bin2 = '../tmp/blue2_tmp.bin'
                    bin3 = '../tmp/blue3_tmp.bin'
                    bin4 = '../tmp/blue4_tmp.bin'
                    
                    fitsfname1 = '{}/{}.fits'.format(self.current_fits_outdir1, self.fits_basename)
                    fitsfname2 = '{}/{}.fits'.format(self.current_fits_outdir2, self.fits_basename)
                    fitsfname3 = '{}/{}.fits'.format(self.current_fits_outdir3, self.fits_basename)
                    fitsfname4 = '{}/{}.fits'.format(self.current_fits_outdir4, self.fits_basename)
                    
#                    _thread.start_new_thread( self.thread_socket_bin2fits, (bin1,) ) 
#                    _thread.start_new_thread( self.thread_socket_bin2fits, (bin2,) ) 
#                    _thread.start_new_thread( self.thread_socket_bin2fits, (bin3,) ) 
#                    _thread.start_new_thread( self.thread_socket_bin2fits, (bin4,) ) 
                    if self.header1_rdy:
                        _thread.start_new_thread( self.thread_bin2fits, (bin1, fitsfname1, 1) )
                    else:
                        self.write_log('header1 is NOT ready')
                    
                    if self.header2_rdy:
                        _thread.start_new_thread( self.thread_bin2fits, (bin2, fitsfname2, 2) )
                    else:
                        self.write_log('header2 is NOT ready')
                    
                    if self.header3_rdy:
                        _thread.start_new_thread( self.thread_bin2fits, (bin3, fitsfname3, 3) )
                    else:
                        self.write_log('header3 is NOT ready')

                    if self.header4_rdy:
                        _thread.start_new_thread( self.thread_bin2fits, (bin4, fitsfname4, 4) )
                    else:
                        self.write_log('header4 is NOT ready')
                    
#                    _thread.start_new_thread( self.thread_socket_openfits, () ) 
                    self.write_log('> fake readout ends')
                    reply = 'readoutdone'
                    time.sleep(0.01)
                    connection.sendall(str.encode(reply))

                    self.header1_rdy = False
                    self.header2_rdy = False
                    self.header3_rdy = False
                    self.header4_rdy = False
                else:
                    self.write_log('**** IP of master CAM has not been set ****')
                    reply = '**** IP of master CAM has not been set ****'
            elif MSG.upper() == 'READOUTY':
                reply = 'readoutdone'
                time.sleep(0.01)
            else:
                reply = 'UNKNOW command: {}'.format(MSG)
                self.write_log(reply)


#            connection.sendall(str.encode(reply))
            
        self.ThreadClientCount -= 1
        self.write_log('socket_client_hander ends ...')
        self.write_log('[Socket Client #] {}'.format(self.ThreadClientCount))

        connection.close()





##########################
    def run(self):
        self.window.mainloop()
